import json
from collections import OrderedDict
import difflib
from tkinter import *
from tkinter import filedialog, messagebox, scrolledtext
from tkinter.ttk import *
import webbrowser

class JSONDiffApp:
    def __init__(self, root):
        self.root = root
        self.root.title("JSON 差异比较工具")
        self.root.geometry("1000x800")
        try:
            from colorama import init
            init(autoreset=True)
        except ImportError:
            pass
        
        self.create_widgets()
        self.setup_menu()
    
    def create_widgets(self):
        main_frame = Frame(self.root)
        main_frame.pack(fill=BOTH, expand=True, padx=10, pady=10)

        input_frame = LabelFrame(main_frame, text="JSON 输入", padding=10)
        input_frame.pack(fill=BOTH, expand=True, pady=(0, 10))

        json1_frame = Frame(input_frame)
        json1_frame.pack(fill=BOTH, expand=True)
        
        Label(json1_frame, text="JSON 1:").pack(anchor=W)
        self.json1_text = scrolledtext.ScrolledText(json1_frame, height=10, wrap=WORD)
        self.json1_text.pack(fill=BOTH, expand=True)
        
        btn_frame = Frame(json1_frame)
        btn_frame.pack(fill=X, pady=(5, 0))
        
        Button(btn_frame, text="从文件加载", command=lambda: self.load_json_file(self.json1_text)).pack(side=LEFT)
        Button(btn_frame, text="清空", command=lambda: self.clear_text(self.json1_text)).pack(side=LEFT, padx=(5, 0))

        json2_frame = Frame(input_frame)
        json2_frame.pack(fill=BOTH, expand=True, pady=(10, 0))
        
        Label(json2_frame, text="JSON 2:").pack(anchor=W)
        self.json2_text = scrolledtext.ScrolledText(json2_frame, height=10, wrap=WORD)
        self.json2_text.pack(fill=BOTH, expand=True)
        
        btn_frame2 = Frame(json2_frame)
        btn_frame2.pack(fill=X, pady=(5, 0))
        
        Button(btn_frame2, text="从文件加载", command=lambda: self.load_json_file(self.json2_text)).pack(side=LEFT)
        Button(btn_frame2, text="清空", command=lambda: self.clear_text(self.json2_text)).pack(side=LEFT, padx=(5, 0))
        
        # 比较选项
        options_frame = LabelFrame(main_frame, text="比较选项", padding=10)
        options_frame.pack(fill=X, pady=(0, 10))
        
        self.sort_keys_var = BooleanVar(value=True)
        Checkbutton(options_frame, text="排序键名比较", variable=self.sort_keys_var).pack(anchor=W)
        
        # 按钮区域
        btn_area = Frame(main_frame)
        btn_area.pack(fill=X, pady=(0, 10))
        
        Button(btn_area, text="比较 (文本格式)", command=self.compare_json_text).pack(side=LEFT)
        Button(btn_area, text="比较 (字典格式)", command=self.compare_json_dict).pack(side=LEFT, padx=(10, 0))
        Button(btn_area, text="清除结果", command=self.clear_results).pack(side=RIGHT)
        
        # 结果区域
        result_frame = LabelFrame(main_frame, text="比较结果", padding=10)
        result_frame.pack(fill=BOTH, expand=True)
        
        # 创建笔记本控件
        self.notebook = Notebook(result_frame)
        self.notebook.pack(fill=BOTH, expand=True)
        
        # 文本格式结果标签页
        self.text_result_tab = Frame(self.notebook)
        self.notebook.add(self.text_result_tab, text="文本格式")
        
        self.text_result = scrolledtext.ScrolledText(self.text_result_tab, wrap=WORD, state=DISABLED)
        self.text_result.pack(fill=BOTH, expand=True)
        
        # 配置文本颜色标签
        self.text_result.tag_configure("added", foreground="green")
        self.text_result.tag_configure("removed", foreground="red")
        self.text_result.tag_configure("diff", foreground="blue")
        
        # 字典格式结果标签页
        self.dict_result_tab = Frame(self.notebook)
        self.notebook.add(self.dict_result_tab, text="字典格式")
        
        # 字典结果树视图
        self.tree = Treeview(self.dict_result_tab, columns=('type', 'key', 'old_value', 'new_value'), show='headings')
        self.tree.heading('type', text='类型')
        self.tree.heading('key', text='键名')
        self.tree.heading('old_value', text='旧值')
        self.tree.heading('new_value', text='新值')
        self.tree.column('type', width=100, minwidth=100)
        self.tree.column('key', width=150, minwidth=150)
        self.tree.column('old_value', width=250, minwidth=250)
        self.tree.column('new_value', width=250, minwidth=250)
        self.tree.pack(fill=BOTH, expand=True, padx=5, pady=5)
        
        # 添加滚动条
        scrollbar = Scrollbar(self.dict_result_tab, orient="vertical", command=self.tree.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        # 状态栏
        self.status = StringVar()
        self.status_bar = Label(self.root, textvariable=self.status, borderwidth=1, relief=SUNKEN, anchor=W)

        self.status_bar.pack(side=BOTTOM, fill=X)
    
    def setup_menu(self):
        menubar = Menu(self.root)
        
        # 文件菜单
        file_menu = Menu(menubar, tearoff=0)
        file_menu.add_command(label="打开 JSON1", command=lambda: self.load_json_file(self.json1_text))
        file_menu.add_command(label="打开 JSON2", command=lambda: self.load_json_file(self.json2_text))
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.root.quit)
        menubar.add_cascade(label="文件", menu=file_menu)
        
        # 帮助菜单
        help_menu = Menu(menubar, tearoff=0)
        help_menu.add_command(label="关于", command=self.show_about)
        menubar.add_cascade(label="帮助", menu=help_menu)
        
        self.root.config(menu=menubar)
    
    def load_json_file(self, text_widget):
        file_path = filedialog.askopenfilename(
            title="选择 JSON 文件",
            filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")]
        )
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                text_widget.delete(1.0, END)
                text_widget.insert(END, content)
                self.status.set(f"已加载: {file_path}")
            except Exception as e:
                messagebox.showerror("错误", f"无法加载文件: {e}")
                self.status.set("加载文件失败")
    
    def clear_text(self, text_widget):
        text_widget.delete(1.0, END)
        self.status.set("文本已清空")
    
    def clear_results(self):
        self.text_result.config(state=NORMAL)
        self.text_result.delete(1.0, END)
        self.text_result.config(state=DISABLED)
        
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        self.status.set("结果已清除")
    
    def compare_json_text(self):
        try:
            json1 = self.load_json(self.json1_text.get(1.0, END))
            json2 = self.load_json(self.json2_text.get(1.0, END))
            
            if not json1 or not json2:
                raise ValueError("无效的 JSON 数据")
            
            sort_keys = self.sort_keys_var.get()
            diff = self.compare_json(json1, json2, 'text', sort_keys)
            
            self.text_result.config(state=NORMAL)
            self.text_result.delete(1.0, END)
            
            # 应用颜色标签
            if diff == "JSON相同":
                self.text_result.insert(END, diff)
            else:
                lines = diff.split('\n')
                for line in lines:
                    if line.startswith('++'):
                        self.text_result.insert(END, line[2:] + '\n', "added")
                    elif line.startswith('--'):
                        self.text_result.insert(END, line[2:] + '\n', "removed")
                    elif line.startswith('**'):
                        self.text_result.insert(END, line[2:] + '\n', "diff")
                    else:
                        self.text_result.insert(END, line + '\n')
            
            self.text_result.config(state=DISABLED)
            self.notebook.select(self.text_result_tab)
            self.status.set("文本格式比较完成")
        except Exception as e:
            messagebox.showerror("错误", f"比较时出错: {e}")
            self.status.set(f"错误: {e}")
    
    def compare_json_dict(self):
        try:
            json1 = self.load_json(self.json1_text.get(1.0, END))
            json2 = self.load_json(self.json2_text.get(1.0, END))
            
            if not json1 or not json2:
                raise ValueError("无效的 JSON 数据")
            
            sort_keys = self.sort_keys_var.get()
            diff_dict = self.compare_json(json1, json2, 'dict', sort_keys)
            
            # 清除树视图
            for item in self.tree.get_children():
                self.tree.delete(item)
            
            if not diff_dict['same']:
                # 添加删除的项
                for key in diff_dict.get('removed', []):
                    self.tree.insert('', END, values=('删除', key, '', ''))
                
                # 添加新增的项
                for key in diff_dict.get('added', []):
                    self.tree.insert('', END, values=('新增', key, '', ''))
                
                # 添加修改的项
                for item in diff_dict.get('changed', []):
                    old_value = json.dumps(item['old_value'], ensure_ascii=False) if item['old_value'] else "null"
                    new_value = json.dumps(item['new_value'], ensure_ascii=False) if item['new_value'] else "null"
                    self.tree.insert('', END, values=('修改', item['key'], old_value, new_value))
            
            self.notebook.select(self.dict_result_tab)
            self.status.set("字典格式比较完成")
        except Exception as e:
            messagebox.showerror("错误", f"比较时出错: {e}")
            self.status.set(f"错误: {e}")
    
    def load_json(self, json_input):
        try:
            if isinstance(json_input, str):
                try:
                    # 尝试作为文件路径打开
                    with open(json_input, 'r', encoding='utf-8') as f:
                        return json.load(f, object_pairs_hook=OrderedDict)
                except (FileNotFoundError, json.JSONDecodeError):
                    # 否则作为 JSON 字符串解析
                    return json.loads(json_input, object_pairs_hook=OrderedDict)
            return json_input
        except Exception as e:
            raise ValueError(f"无法解析JSON输入: {e}")
    
    def compare_json(self, json1, json2, output_format='text', sort_keys=True):
        if sort_keys:
            if not isinstance(json1, OrderedDict) and isinstance(json1, dict):
                json1 = OrderedDict(sorted(json1.items()))
            if not isinstance(json2, OrderedDict) and isinstance(json2, dict):
                json2 = OrderedDict(sorted(json2.items()))
        
        if output_format == 'text':
            str1 = json.dumps(json1, indent=2, ensure_ascii=False, sort_keys=sort_keys)
            str2 = json.dumps(json2, indent=2, ensure_ascii=False, sort_keys=sort_keys)
            
            differ = difflib.unified_diff(
                str1.splitlines(keepends=True),
                str2.splitlines(keepends=True),
                fromfile='JSON1',
                tofile='JSON2',
                lineterm=''
            )
            
            diff = ''.join(differ)
            
            if not diff:
                return "JSON相同"
            
            # 修改diff格式，去掉前缀字符
            lines = []
            for line in diff.split('\n'):
                if line.startswith('+++') or line.startswith('---'):
                    continue
                if line.startswith('@@'):
                    lines.append(line)
                elif line.startswith('+'):
                    lines.append(f"++{line[1:]}")
                elif line.startswith('-'):
                    lines.append(f"--{line[1:]}")
                elif line.startswith('^'):
                    lines.append(f"**{line[1:]}")
                else:
                    lines.append(line)
            
            return '\n'.join(lines)
        
        elif output_format == 'dict':
            diff_dict = {
                'added': [],
                'removed': [],
                'changed': [],
                'same': True
            }
            
            if not self._compare_values(json1, json2):
                diff_dict['same'] = False
                if isinstance(json1, dict) and isinstance(json2, dict):
                    all_keys = set(json1.keys()) | set(json2.keys())
                    for key in all_keys:
                        val1 = json1.get(key, None)
                        val2 = json2.get(key, None)
                        if key not in json2:
                            diff_dict['removed'].append(key)
                        elif key not in json1:
                            diff_dict['added'].append(key)
                        elif val1 != val2:
                            diff_dict['changed'].append({
                                'key': key,
                                'old_value': val1,
                                'new_value': val2
                            })
            return diff_dict
        
        else:
            str1 = json.dumps(json1, indent=2, ensure_ascii=False, sort_keys=sort_keys)
            str2 = json.dumps(json2, indent=2, ensure_ascii=False, sort_keys=sort_keys)
            return str1 == str2
    
    def _compare_values(self, v1, v2):
        if isinstance(v1, dict) and isinstance(v2, dict):
            if set(v1.keys()) != set(v2.keys()):
                return False
            for k in v1:
                if not self._compare_values(v1[k], v2[k]):
                    return False
            return True
        elif isinstance(v1, list) and isinstance(v2, list):
            if len(v1) != len(v2):
                return False
            for i in range(len(v1)):
                if not self._compare_values(v1[i], v2[i]):
                    return False
            return True
        else:
            return v1 == v2
    
    def show_about(self):
        about_text = """JSON 差异比较工具 v1.0

一个用于比较两个 JSON 数据差异的实用工具。

作者: LIU Chunxin
地址: https://gitee.com/ED-NY/compare-json-tool
"""
        messagebox.showinfo("关于", about_text)

def main():
    root = Tk()
    app = JSONDiffApp(root)
    root.mainloop()

if __name__ == '__main__':
    main()